package com.jd.jim.client.client;

import com.alibaba.fastjson.JSONObject;
import com.jd.jim.client.config.AppConfiguration;
import com.jd.jim.client.init.JIMClientHandleInitializer;
import com.jd.jim.client.vo.req.GoogleProtocolVO;
import com.jd.jim.client.vo.req.LoginReqVO;
import com.jd.jim.client.vo.res.JIMServerResVO;
import com.jd.jim.client.service.EchoService;
import com.jd.jim.client.service.MsgHandle;
import com.jd.jim.client.service.ReConnectManager;
import com.jd.jim.client.service.RouteRequest;
import com.jd.jim.client.service.impl.ClientInfo;
import com.jd.jim.client.thread.ContextHolder;
import com.jd.jim.common.constant.Constants;
import com.jd.jim.common.protocol.JIMReqMsg;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author ：sizegang
 * @description：
 * @version:  1.0
 */
@Component
@Slf4j
public class JIMClient {


    private EventLoopGroup group = new NioEventLoopGroup(1, new DefaultThreadFactory("jim-work"));

    @Value("${jim.user.id}")
    private long userId;

    @Value("${jim.user.userName}")
    private String userName;

    private SocketChannel channel;

    @Autowired
    private EchoService echoService;

    @Autowired
    private RouteRequest routeRequest;

    @Autowired
    private AppConfiguration configuration;

    @Autowired
    private MsgHandle msgHandle;

    @Autowired
    private ClientInfo clientInfo;

    @Autowired
    private ReConnectManager reConnectManager;


    /**
     * 重试次数
     */
    private int errorCount;

    /**
     * 客户端初始化
     * @throws Exception
     */
    @PostConstruct
    public void start() throws Exception {

        //登录 + 获取可以使用的服务器 ip+port
        JIMServerResVO.ServerInfo timServer = userLogin();

        //根据命中的IM server 启动客户端 （安卓、PC、IOS 可以根据自己的业务实现websocket 与对应的IM server进行长链接）
        startClient(timServer);

        //向服务端注册 进行tcp建立长链接
        loginTIMServer();
    }

    /**
     * 启动客户端 模拟 安卓 pc ios的IM 客户端 例如： 微信、钉钉、飞书 等
     * netty client 如果是 安卓 、 PC 、IOS 使用websockt 那一套一样可以实现与后端 netty 集群进行打通，
     * 这里我们是后端开发工程师，所有没有去特意写一套websockt客户端集成IM netty client 的效果和 CS端的client 一样
     *
     * @param timServer
     * @throws Exception
     */
    private void startClient(JIMServerResVO.ServerInfo timServer) {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new JIMClientHandleInitializer());

        ChannelFuture future = null;
        try {
            future = bootstrap.connect(timServer.getIp(), timServer.getTimServerPort()).sync();
        } catch (Exception e) {
            errorCount++;

            if (errorCount >= configuration.getErrorCount()) {
                log.error("连接失败次数达到上限[{}]次", errorCount);
                msgHandle.shutdown();
            }
            log.error("Connect fail!", e);
        }
        if (future.isSuccess()) {
            echoService.echo("futurs response  client reConnect success! server is  " + JSONObject.toJSONString(timServer));
            log.info("启动 jim client 成功");
        }
        // 赋值 channel 给客户端
        channel = (SocketChannel) future.channel();
    }

    /**
     * 登录+路由服务器, 登录成功， 返回命中的 IM server
     *
     * @return 路由服务器信息
     * @throws Exception
     */
    private JIMServerResVO.ServerInfo userLogin() {
        LoginReqVO loginReqVO = new LoginReqVO(userId, userName);
        JIMServerResVO.ServerInfo timServer = null;
        try {
            // 从 网关获取命中的IM 服务器
            timServer = routeRequest.getTIMServer(loginReqVO);

            //保存系统信息
            clientInfo.saveServiceInfo(timServer.getIp() + ":" + timServer.getTimServerPort())
                    .saveUserInfo(userId, userName);

            log.info("timServer=[{}]", timServer.toString());
        } catch (Exception e) {
            errorCount++;

            if (errorCount >= configuration.getErrorCount()) {
                echoService.echo("The maximum number of reconnections has been reached[{}]times, close jim client!", errorCount);
                msgHandle.shutdown();
            }
            log.error("login fail", e);
        }
        return timServer;
    }

    /**
     * 向服务器注册
     */
    private void loginTIMServer() {
        JIMReqMsg login = new JIMReqMsg(userId, userName, Constants.CommandType.LOGIN);
        ChannelFuture future = channel.writeAndFlush(login);
        future.addListener((ChannelFutureListener) channelFuture ->
                echoService.echo("Registry jim server success!")
        );
    }

    /**
     * 发送消息字符串
     *
     * @param msg
     */
    public void sendStringMsg(String msg) {
        ByteBuf message = Unpooled.buffer(msg.getBytes().length);
        message.writeBytes(msg.getBytes());
        ChannelFuture future = channel.writeAndFlush(message);
        future.addListener((ChannelFutureListener) channelFuture ->
                log.info("客户端手动发消息成功={}", msg));

    }

    /**
     * 发送 Google Protocol 编解码字符串
     *
     * @param googleProtocolVO
     */
    public void sendGoogleProtocolMsg(GoogleProtocolVO googleProtocolVO) {

        JIMReqMsg protocol = new JIMReqMsg(googleProtocolVO.getRequestId(), googleProtocolVO.getMsg(), Constants.CommandType.MSG);
        ChannelFuture future = channel.writeAndFlush(protocol);
        future.addListener((ChannelFutureListener) channelFuture ->
                log.info("客户端手动发送 Google Protocol 成功={}", googleProtocolVO.toString()));

    }


    /**
     * 1. clear route information.
     * 2. reconnect.
     * 3. shutdown reconnect job.
     * 4. reset reconnect state.
     *
     * @throws Exception
     */
    public void reconnect() throws Exception {
        if (channel != null && channel.isActive()) {
            return;
        }
        //首先清除路由信息 redis，下线
        routeRequest.offLine();
        echoService.echo("jim server shutdown, reconnecting....");
        Thread.sleep(1000); // 延迟一下 防止将下方重新连接的路由信息删除
//        echoService.echo("jim server shutdown, start....");
        start();
        echoService.echo("Great! reConnect success!!!");
        // shutdown reconnect job
        reConnectManager.reConnectSuccess();
        // threadlocal 维护重连状态 （这里重置reconnet state）
        ContextHolder.clear();
    }

    /**
     * 关闭
     *
     * @throws InterruptedException
     */
    public void close() throws InterruptedException {
        if (channel != null) {
            channel.close();
        }
    }
}
